package pl.vanta.utils.commons.formats;

import java.text.DecimalFormat;
import java.text.FieldPosition;
import java.text.NumberFormat;
import java.text.ParsePosition;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

public class TimeFormat extends NumberFormat
{
	private static final long				serialVersionUID	= 6551000172170945707L;

	public static final String				PATTERN_SEPARATOR	= "%|";
	public static final String				PATTERN_DAYS		= "%d";
	public static final String				PATTERN_HOURS		= "%h";
	public static final String				PATTERN_MINUTES		= "%m";
	public static final String				PATTERN_SECODNS		= "%s";
	public static final String				PATTERN_MILLIS		= "%l";
	public static final String				PATTERN_MICROS		= "%u";
	public static final String				PATTERN_NANOS		= "%n";

	private static Map<TimeUnit, String>	patternSigns		= new HashMap<TimeUnit, String>();
	private static Map<TimeUnit, Long>		dividers			= new HashMap<TimeUnit, Long>();

	static
	{
		patternSigns.put(TimeUnit.DAYS, PATTERN_DAYS);
		patternSigns.put(TimeUnit.HOURS, PATTERN_HOURS);
		patternSigns.put(TimeUnit.MINUTES, PATTERN_MINUTES);
		patternSigns.put(TimeUnit.SECONDS, PATTERN_SECODNS);
		patternSigns.put(TimeUnit.MILLISECONDS, PATTERN_MILLIS);
		patternSigns.put(TimeUnit.MICROSECONDS, PATTERN_MICROS);
		patternSigns.put(TimeUnit.NANOSECONDS, PATTERN_NANOS);

		dividers.put(TimeUnit.DAYS, 1L * 1000 * 1000 * 1000 * 60 * 60 * 24);
		dividers.put(TimeUnit.HOURS, 1L * 1000 * 1000 * 1000 * 60 * 60);
		dividers.put(TimeUnit.MINUTES, 1L * 1000 * 1000 * 1000 * 60);
		dividers.put(TimeUnit.SECONDS, 1L * 1000 * 1000 * 1000);
		dividers.put(TimeUnit.MILLISECONDS, 1L * 1000 * 1000);
		dividers.put(TimeUnit.MICROSECONDS, 1L * 1000);
		dividers.put(TimeUnit.NANOSECONDS, 1L);
	}

	private String							pattern;

	private String							separator;

	private Map<TimeUnit, String>			prefixes;
	private Map<TimeUnit, String>			postfixes;

	private Map<TimeUnit, String>			patterns;

	private TimeUnit						baseUnit;

	public TimeFormat(String pattern)
	{
		this(pattern, TimeUnit.MILLISECONDS, "");
	}

	public TimeFormat(String pattern, TimeUnit baseUnit)
	{
		this(pattern, baseUnit, "");
	}

	public TimeFormat(String pattern, TimeUnit baseUnit, String separator)
	{
		if (pattern == null || "".equals(pattern))
		{
			throw new IllegalArgumentException("Pattern cannot be empty");
		}

		if (baseUnit == null)
		{
			throw new IllegalArgumentException("Base unit cannot be null");
		}

		if (separator == null)
		{
			throw new IllegalArgumentException("Separator cannot be null");
		}

		if (!checkPattern(pattern))
		{
			throw new IllegalArgumentException("Illegal pattern");
		}

		this.baseUnit = baseUnit;
		this.pattern = pattern;
		this.separator = separator;

		this.prefixes = new HashMap<TimeUnit, String>();
		this.postfixes = new HashMap<TimeUnit, String>();

		this.patterns = new HashMap<TimeUnit, String>();

		this.patterns.put(TimeUnit.DAYS, "#");
		this.patterns.put(TimeUnit.HOURS, "#");
		this.patterns.put(TimeUnit.MINUTES, "00");
		this.patterns.put(TimeUnit.SECONDS, "00");
		this.patterns.put(TimeUnit.MILLISECONDS, "000");
		this.patterns.put(TimeUnit.MICROSECONDS, "000");
		this.patterns.put(TimeUnit.NANOSECONDS, "000");
	}

	private boolean checkPattern(String string)
	{
		boolean found = false;

		for (String pattern : patternSigns.values())
		{
			if (string.indexOf(pattern) != -1)
			{
				found = true;
				break;
			}
		}

		return found;
	}

	@Override
	public StringBuffer format(double number, StringBuffer sb, FieldPosition pos)
	{
		return format(Math.round(number), sb, pos);
	}

	@Override
	public StringBuffer format(long number, StringBuffer sb, FieldPosition pos)
	{
		boolean first = true;
		long nanos = this.baseUnit.toNanos(number);

		String result = this.pattern;

		for (Map.Entry<TimeUnit, String> entry : patternSigns.entrySet())
		{
			String temp = "";

			TimeUnit unit = entry.getKey();
			String patternSign = entry.getValue();

			int index = this.pattern.indexOf(patternSign);

			if (index != -1)
			{
				long divider = dividers.get(unit);

				long value = nanos / divider;
				nanos -= (value * divider);

				String valueString = formatValue(unit, value);

				if (valueString != null)
				{
					temp = "";
					temp += nullToEmpty(this.prefixes.get(unit));
					temp += valueString;
					temp += nullToEmpty(this.postfixes.get(unit));

					if (!first)
					{
						temp = this.separator + temp;
					}

					first = false;

					result = result.replaceAll(patternSign, temp.toString());
				}
			}
		}

		return sb.append(result);
	}

	private String formatValue(TimeUnit unit, long value)
	{
		String pattern = this.patterns.get(unit);

		if (pattern != null && !"".equals(pattern))
		{
			DecimalFormat df = new DecimalFormat(pattern);

			String stringValue = df.format(value);

			// to fix the 'bug' in DecimalFormat
			if (value == 0 && pattern == "#")
			{
				return "";
			}

			return stringValue;
		}
		else
		{
			return "";
		}
	}

	@Override
	public Number parse(String source, ParsePosition parsePosition)
	{
		return null;
	}

	private String nullToEmpty(String string)
	{
		if (string == null)
		{
			return "";
		}
		else
		{
			return string;
		}
	}

	public void setPrefix(TimeUnit unit, String prefix)
	{
		this.prefixes.put(unit, prefix);
	}

	public void setPostfix(TimeUnit unit, String prefix)
	{
		this.postfixes.put(unit, prefix);
	}

	public void setPattern(TimeUnit unit, String pattern)
	{
		this.patterns.put(unit, pattern);
	}

	public void setPatterns(Map<TimeUnit, String> patterns)
	{
		this.patterns = patterns;
	}

	public String getPattern()
	{
		return this.pattern;
	}

	public void setPattern(String pattern)
	{
		if (checkPattern(pattern))
		{
			this.pattern = pattern;
		}
		else
		{
			throw new IllegalArgumentException("Illegal pattern");
		}
	}
}